/******************************************************************************/
package jamie.org.sil.www.usxUtils;

import java.util.HashMap;


/******************************************************************************/
/**
 * Miscellaneous support for Bible reference parsing and rendering.
 * <p>
 *
 * In particular, this class provides lists of books in ascending order of
 * length for use when parsing; and unique integer indicators used to show
 * the types of entity which appear in the ParsedTextWithReferencesNew
 * instances.
 * <p>
 *
 * Note that separators have different identifiers according to whether they
 * are "internal" separators (ie used as input in USFM), or "external" ones
 * (used for output purposes).  These identifiers are each made up of two
 * parts -- a bit setting which indicates whether we are dealing with an
 * internal or external separator, and a value which indicates what kind
 * of separator we have.  You can use the numbers simply as opaque identifiers,
 * or you may, where appropriate, use a knowledge of this structure.
 *
 * @author ARA "Jamie" Jamieson
**/

public class BibleReferenceSupport
{
  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                 Public                                 **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/


  /****************************************************************************/
  /* Token-types. */
  
  public static final int C_Separator_ChapterAndVerse                =  1;
  public static final int C_Separator_RangeSameChapter               =  2;
  public static final int C_Separator_RangeDifferentChapters         =  3;
  public static final int C_Separator_MultiSameChapter               =  4;
  public static final int C_Separator_MultiDifferentChapters         =  5;

  public static final int C_SeparatorInternal                        =  128;
  public static final int C_SeparatorInternal_ChapterAndVerse        =  C_SeparatorInternal + C_Separator_ChapterAndVerse;
  public static final int C_SeparatorInternal_RangeSameChapter       =  C_SeparatorInternal + C_Separator_RangeSameChapter;
  public static final int C_SeparatorInternal_RangeDifferentChapters =  C_SeparatorInternal + C_Separator_RangeDifferentChapters;
  public static final int C_SeparatorInternal_MultiSameChapter       =  C_SeparatorInternal + C_Separator_MultiSameChapter;
  public static final int C_SeparatorInternal_MultiDifferentChapters =  C_SeparatorInternal + C_Separator_MultiDifferentChapters;

  public static final int C_SeparatorExternal                        =  256;
  public static final int C_SeparatorExternal_ChapterAndVerse        =  C_SeparatorExternal + C_Separator_ChapterAndVerse;
  public static final int C_SeparatorExternal_RangeSameChapter       =  C_SeparatorExternal + C_Separator_RangeSameChapter;
  public static final int C_SeparatorExternal_RangeDifferentChapters =  C_SeparatorExternal + C_Separator_RangeDifferentChapters;
  public static final int C_SeparatorExternal_MultiSameChapter       =  C_SeparatorExternal + C_Separator_MultiSameChapter;
  public static final int C_SeparatorExternal_MultiDifferentChapters =  C_SeparatorExternal + C_Separator_MultiDifferentChapters;


  /****************************************************************************/
  /* Langauge and length selectors. */
  
  public enum Language { C_English, C_Vernacular };
  public enum Length { C_Abbreviated, C_Full, C_AsSupplied };
  

  /****************************************************************************/
  /**
     Singleton.

     @return Singleton instance.
  */

  public static BibleReferenceSupport instance ()
  {
    if (null == m_Instance) m_Instance = new BibleReferenceSupport();
    return m_Instance;
  }


  /****************************************************************************/
  /**
   * Given a type from the list of integer type codes, return the "basic"
   * code (ie, for separators, the code for the separator itself, regardless
   * of whether we are dealing with internal or external separators).
   *
   * @param Code.
   *
   * @return Base code.
   */

  public int getBaseCode (int code)
  {
    return code & ~(C_SeparatorInternal + C_SeparatorExternal);
  }


  /****************************************************************************/
  /**
   * Gets the list of booknames in ascending length order, in lower case.
   * 
   * @return List of book names.
   */
  
  public String[] getBookNamesInAscendingLengthOrderLowerCase ()
  {
    return m_BookNamesInAscendingLengthOrderLowerCase;
  }
  
  
  /****************************************************************************/
  /**
   * Gets the list of booknames in ascending length order, in correct case.
   * 
   * @return List of book names.
   */
  
  public String[] getBookNamesInAscendingLengthOrderCorrectCase ()
  {
    return m_BookNamesInAscendingLengthOrderCorrectCase;
  }
  
  
  /****************************************************************************/
  /**
   * Gets the first or only external chapter and verse separator.
   * 
   * @return Separator.
   */
  
  public String getExternalChapterAndVerseSeparator ()
  {
    return m_CodeToSeparatorMapping.get(C_SeparatorExternal_ChapterAndVerse); 
  }
  
  
  /****************************************************************************/
  /**
   * Gets the first or only internal chapter and verse separator.
   * 
   * @return Separator.
   */
  
  public String getInternalChapterAndVerseSeparator ()
  {
    return m_CodeToSeparatorMapping.get(C_SeparatorInternal_ChapterAndVerse); 
  }
  
  
  /****************************************************************************/
  /**
   * Returns a separator in a given style.  If the actual separator supplied
   * to the routine is in the required style, then the actual separator is
   * returned.  Otherwise it is converted to the canonical form of the
   * required style.
   *
   * @param requiredStyle Drawn from C_SeparatorInternal / C_SeparatorExternal.
   *
   * @param actualSep Separator as supplied (which may contain spaces etc).
   *
   * @param actualStyle Style in which separator has been supplied.
   *
   * @return Separator in required style.
   */

  public String getSeparator (int requiredStyle, String actualSep, int actualStyle)
  {
    if (0 != (actualStyle & requiredStyle)) return actualSep;
    return m_CodeToSeparatorMapping.get(getBaseCode(actualStyle) | requiredStyle);
  }


  /****************************************************************************/
  /**
   * Says whether "sep" is a chapter and verse separator.
   * 
   * @param sep Putative chapter-and-verse separator.
   * 
   * @return Type of separator, or -1 if not a separator.
   */
  
  public int isChapterAndVerseSeparator (String sep)
  {
    sep = sep.trim();
    if (!m_SeparatorToCodeMapping.containsKey(sep)) return -1;
    int type = m_SeparatorToCodeMapping.get(sep);
    if (C_SeparatorInternal_ChapterAndVerse == type ||
        C_SeparatorExternal_ChapterAndVerse == type)
      return type;
    else
      return -1;
  }
  
  
  /****************************************************************************/
  /**
   * Says whether "code" represents a multi separator.
   * 
   * @param code Code for putative separator.
   * 
   * @return Type of separator, or -1 if not a multi separator.
   * */
  
  public int isMultiSeparator (int code)
  {
    if (!m_CodeToSeparatorMapping.containsKey(code)) return -1;
    code = code & ~(C_SeparatorInternal + C_SeparatorExternal);
    return (C_Separator_MultiSameChapter == code || C_Separator_MultiDifferentChapters == code) ? code : -1;
  }


  /****************************************************************************/
  /**
   * Says whether "code" represents a range separator.
   * 
   * @param code Code for putative separator.
   * 
   * @return Type of separator, or -1 if not a range separator.
   * */
  
  public int isRangeSeparator (int code)
  {
    if (!m_CodeToSeparatorMapping.containsKey(code)) return -1;
    code = code & ~(C_SeparatorInternal + C_SeparatorExternal);
    return (C_Separator_RangeSameChapter == code || C_Separator_RangeDifferentChapters == code) ? code : -1;
  }


  /****************************************************************************/
  /**
   * Says whether "code" represents a separator.
   * 
   * @param code Code for putative separator.
   * 
   * @return Type of separator, or -1 if not a separator.
   * */
  
  public int isSeparator (int code)
  {
    if (!m_CodeToSeparatorMapping.containsKey(code)) return -1;
    return code;
  }


  /****************************************************************************/
  /**
   * Says whether "sep" is a separator.
   * 
   * @param sep Putative separator.
   * 
   * @return Type of separator, or -1 if not a separator.
   * */
  
  public int isSeparator (String sep)
  {
    sep = sep.replaceAll("\\p{Z}+", "");
    sep = sep.replaceAll("\\p{C}+", ""); // Removes eg "RTL" marker.
    if (!m_SeparatorToCodeMapping.containsKey(sep)) return -1;
    return m_SeparatorToCodeMapping.get(sep);
  }




  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                 Private                                **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  private BibleReferenceSupport ()
  {
    Config cfg = Config.instance();
    
    addToSeparators(cfg.getInternalSeparatorChapterAndVerse(), C_SeparatorInternal_ChapterAndVerse);
    addToSeparators(cfg.getInternalSeparatorRangeSameChapter(), C_SeparatorInternal_RangeSameChapter);
    addToSeparators(cfg.getInternalSeparatorRangeDifferentChapters(), C_SeparatorInternal_RangeDifferentChapters);
    addToSeparators(cfg.getInternalSeparatorMultiSameChapter(), C_SeparatorInternal_MultiSameChapter);
    addToSeparators(cfg.getInternalSeparatorMultiDifferentChapters(), C_SeparatorInternal_MultiDifferentChapters);

    addToSeparators(cfg.getExternalSeparatorChapterAndVerse(), C_SeparatorExternal_ChapterAndVerse);
    addToSeparators(cfg.getExternalSeparatorRangeSameChapter(), C_SeparatorExternal_RangeSameChapter);
    addToSeparators(cfg.getExternalSeparatorRangeDifferentChapters(), C_SeparatorExternal_RangeDifferentChapters);
    addToSeparators(cfg.getExternalSeparatorMultiSameChapter(), C_SeparatorExternal_MultiSameChapter);
    addToSeparators(cfg.getExternalSeparatorMultiDifferentChapters(), C_SeparatorExternal_MultiDifferentChapters);
    
    m_BookNamesInAscendingLengthOrderCorrectCase =
      Utils.sortByAscendingLength(BibleDetails.instance().getFullAndAbbreviatedVernacularNamesAsArray());

    m_BookNamesInAscendingLengthOrderLowerCase =
      Utils.sortByAscendingLength(BibleDetails.instance().getFullAndAbbreviatedVernacularNamesAsArray());

    for (int i = 0; i < m_BookNamesInAscendingLengthOrderLowerCase.length; ++i)
      m_BookNamesInAscendingLengthOrderLowerCase[i] = m_BookNamesInAscendingLengthOrderLowerCase[i].toLowerCase();
 }

  
 /****************************************************************************/
  /* Adds a given separator to the separator list. */
  
  private void addToSeparators (String sep, int type)
  {
    sep = sep.replace("\\p{Z}+", " ").trim();
    String[] seps = sep.split(" ");
    for (int i = 0; i < seps.length; ++i) m_SeparatorToCodeMapping.put(seps[i], type);
    for (int i = 0; i < seps.length; ++i) m_CodeToSeparatorMapping.put(type, seps[i]);
  }
  
  
  /****************************************************************************/
  private static BibleReferenceSupport m_Instance = null;
  private String[] m_BookNamesInAscendingLengthOrderCorrectCase = null;
  private String[] m_BookNamesInAscendingLengthOrderLowerCase = null;
  private HashMap<Integer, String> m_CodeToSeparatorMapping = new HashMap<Integer, String>();
  private HashMap<String, Integer> m_SeparatorToCodeMapping = new HashMap<String, Integer>();
}
